package com.mindata.blockchain.core.controller;

import com.mindata.blockchain.ApplicationContextProvider;
import com.mindata.blockchain.block.Block;
import com.mindata.blockchain.block.check.BlockChecker;
import com.mindata.blockchain.common.ResultPack;
import com.mindata.blockchain.common.TrustSDK;
import com.mindata.blockchain.common.exception.TrustSDKException;
import com.mindata.blockchain.core.bean.BaseData;
import com.mindata.blockchain.core.bean.RaftConfigData;
import com.mindata.blockchain.core.bean.ResultCode;
import com.mindata.blockchain.core.bean.ResultGenerator;
import com.mindata.blockchain.core.dao.DbSQLManager;
import com.mindata.blockchain.core.event.DbSyncEvent;
import com.mindata.blockchain.core.manager.DbBlockManager;
import com.mindata.blockchain.core.manager.RaftManager;
import com.mindata.blockchain.core.manager.SyncManager;
import com.mindata.blockchain.core.vo.InstructionVo;
import com.mindata.blockchain.socket.body.RpcBlockBody;
import com.mindata.blockchain.socket.client.PacketSender;
import com.mindata.blockchain.socket.packet.BlockPacket;
import com.mindata.blockchain.socket.packet.PacketBuilder;
import com.mindata.blockchain.socket.packet.PacketType;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.domain.Pageable;
import org.springframework.data.web.PageableDefault;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

/**
 * @author wuweifeng wrote on 2018/3/7.
 */
@RestController
@RequestMapping("/block")
@Api(description = "区块操作")
public class BlockController {
    private static final Logger logger = LoggerFactory.getLogger(BlockController.class);
    @Resource
    private PacketSender packetSender;
    @Resource
    private DbBlockManager dbBlockManager;
    @Resource
    private SyncManager syncManager;
    @Resource
    private BlockChecker blockChecker;
    @Resource
    private DbSQLManager dbSQLManager;
    @Resource
    private RaftManager raftManager;


    /**
     * 获取最后一个block的信息
     */
    @ApiOperation(value = "获取最后一个block的信息")
    @GetMapping("getLastBlock")
    public BaseData getLastBlock() {
        Block block = dbBlockManager.getLastBlock();
        if (block == null) {
            return ResultGenerator.genFailResult(String.valueOf(ResultCode.NOT_FOUND.code), "不存在");
        }
        return ResultGenerator.genSuccessResult(block);
    }

    @ApiOperation(value = "根据blockHash获取区块信息")
    @GetMapping(path = "getBlockByHash")
    public BaseData getBlockByHash(@ApiParam("区块hash") @RequestParam String blockHash) {
        Block block = dbBlockManager.getBlockByHash(blockHash);
        if (block == null) {
            return ResultGenerator.genFailResult(String.valueOf(ResultCode.NOT_FOUND.code), "不存在");
        }
        return ResultGenerator.genSuccessResult(block);
    }

    @ApiOperation(value = "根据区块高度获取区块信息")
    @GetMapping(path = "getBlockByNumber")
    public BaseData getBlockByNumber(@ApiParam("区块高度") @RequestParam long blockNumber) {
        Block block = dbBlockManager.getBlockByNumber(blockNumber);
        if (block == null) {
            return ResultGenerator.genFailResult(String.valueOf(ResultCode.NOT_FOUND.code), "不存在");
        }
        return ResultGenerator.genSuccessResult(block);
    }

    @ApiOperation(value = "获取节点帐号")
    @GetMapping(path = "getCoinbase")
    public BaseData getCoinbase() {
        String pubKey = dbBlockManager.getCoinbase();
        if (StringUtils.isBlank(pubKey)) {
            return ResultGenerator.genFailResult(String.valueOf(ResultCode.NOT_FOUND.code), "不存在");
        }
        return ResultGenerator.genSuccessResult(pubKey);
    }

    @ApiOperation(value = "修改节点帐号")
    @PostMapping(path = "updateCoinbase")
    public BaseData updateCoinbase(@ApiParam("私钥") @RequestParam String privateKey,
                                   @ApiParam("公钥") @RequestParam String publicKey) throws TrustSDKException {
        if (!TrustSDK.checkPairKey(privateKey, publicKey)) {
            return ResultGenerator.genFailResult("公私钥不匹配");
        }

        dbBlockManager.updateCoinbase(publicKey);
        return ResultGenerator.genSuccessResult();
    }


    @ApiOperation(value = "根据指令hash获取指令内容")
    @GetMapping(path = "getInstructionByHash")
    public BaseData getInstructionByHash(@ApiParam("指令hash") @RequestParam String hash) {
        ResultPack<InstructionVo> resultPack = dbBlockManager.getInstructionByHash(hash);
        if (resultPack.isFailed()) {
            return ResultGenerator.genFailResult(resultPack.comment());
        }
        return ResultGenerator.genSuccessResult(resultPack.value());
    }

    /**
     * 回滚指定区块
     *
     * @param hash
     * @return
     */
    @ApiOperation(value = "回滚指定区块中内容")
    @PostMapping("rollBackBlock")
    public BaseData rollBackBlock(@ApiParam("区块hash") @RequestParam String hash) {
        Block block = dbBlockManager.getBlockByHash(hash);
        if (block == null) {
            return ResultGenerator.genFailResult(String.valueOf(ResultCode.NOT_FOUND.code), "不存在");
        }
        dbSQLManager.rollBack(block);
        return ResultGenerator.genSuccessResult();
    }

    /**
     * 手工执行区块内sql落地到DB操作
     *
     * @param pageable 分页
     * @return 已同步到哪块了的信息
     */
    @ApiOperation(value = "手工执行区块内sql落地到DB操作")
    @GetMapping("sync")
    public BaseData sync(@PageableDefault Pageable pageable) {
        ApplicationContextProvider.publishEvent(new DbSyncEvent(""));
        return ResultGenerator.genSuccessResult(syncManager.findAll(pageable));
    }

    /**
     * 全量检测区块是否正常
     *
     * @return null - 通过
     * hash - 第一个异常hash
     */
    @ApiOperation(value = "全量检测区块是否正常")
    @GetMapping("checkb")
    public BaseData checkAllBlock() {

        Block block = dbBlockManager.getFirstBlock();

        String hash = null;
        while (block != null && hash == null) {
            logger.info("check block:{} done....", block.getBlockHeader().getNumber());
            hash = blockChecker.checkBlock(block);
            block = dbBlockManager.getNextBlock(block);
        }
        return ResultGenerator.genSuccessResult(hash);
    }

    /**
     * 获取下个区块
     *
     * @return
     */
    @ApiOperation(value = "获取下个区块")
    @GetMapping("/next")
    public BaseData nextBlock() {
        Block block = dbBlockManager.getFirstBlock();
        BlockPacket packet = new PacketBuilder<RpcBlockBody>()
                .setType(PacketType.NEXT_BLOCK_INFO_REQUEST.getKey())
                .setBody(new RpcBlockBody(block)).build();
        packetSender.sendGroup(packet);
        return ResultGenerator.genSuccessResult();
    }

    @ApiOperation(value = "获取raft配置")
    @GetMapping("/raft/conf")
    public BaseData raftConf() {
        ResultPack<RaftConfigData> resultPack = raftManager.getConfiguration();
        if (resultPack.isFailed()) {
            return ResultGenerator.genFailResult(resultPack.comment());
        }
        return ResultGenerator.genSuccessResult(resultPack.value());
    }

    @ApiOperation(value = "新增raft节点")
    @PostMapping("/test/raft/addPeers")
    public BaseData addRaft(@ApiParam(defaultValue = "172.16.185.136:5001:1,172.16.185.137:5001:2") @RequestParam String servers) {
        ResultPack<String> resultPack = raftManager.addPeers(servers);
        if (resultPack.isFailed()) {
            return ResultGenerator.genFailResult(resultPack.comment());
        }
        return ResultGenerator.genSuccessResult();
    }

    @ApiOperation(value = "删除raft节点")
    @PostMapping("/test/raft/delPeers")
    public BaseData delRaft(@ApiParam(defaultValue = "172.16.185.136:5001:1,172.16.185.137:5001:2") @RequestParam String servers) {
        ResultPack<String> resultPack = raftManager.delPeers(servers);
        if (resultPack.isFailed()) {
            return ResultGenerator.genFailResult(resultPack.comment());
        }
        return ResultGenerator.genSuccessResult();
    }
}
